﻿//  -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	SecurityService.cs
//  Developer:	Solomatov Igor
//  Created:	15.04.2012
// 
//  -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;

namespace Smart.Common.Security
{
	/// <summary>
	/// Security service
	/// </summary>
	public abstract class SecurityService : ISecurityService
	{
		/// <summary>
		/// Security context
		/// </summary>
		public SecurityContext Ctx { get; set; }

		/// <summary>
		/// Securable items source (lazy)
		/// </summary>
		public SecurableSource Source { get; set; }

		/// <summary>
		/// Security concept (lazy)
		/// </summary>
		public SecurityConcept Concept { get; set; }

		/// <summary>
		/// Init service (also fills lazy properties)
		/// </summary>
		public virtual void InitService()
		{
			if (Source == null)
				Source = CreateSource();

			if (Concept == null)
				Concept = CreateConcept();
		}

		/// <summary>
		/// Create securable source
		/// </summary>
		public abstract SecurableSource CreateSource();

		/// <summary>
		/// Create security concept
		/// </summary>
		public abstract SecurityConcept CreateConcept();

		/// <summary>
		/// Creates security context
		/// </summary>
		public virtual void InitContext(Guid siteId, string serviceName)
		{
			Ctx =
				new SecurityContext(serviceName)
					{
						SiteID = siteId,
						Service = this
					};
		}

		/// <summary>
		/// Create new securable filter
		/// </summary>
		public virtual SecurableFilter CreateFilter()
		{
			return Source.CreateFilter();
		}

		/// <summary>
		/// Get securable object types
		/// </summary>
		public virtual ICollection<SecurableType> GetSecurableTypes()
		{
			return Source.GetSecurableTypes();
		}

		/// <summary>
		/// Ensure all securable security assignments
		/// </summary>
		/// <returns>true, if security changed</returns>
		public virtual bool EnsureSecurity(IDictionary<string, object> values)
		{
			if (values != null)
				Source.LoadValuesToCtx(values);

			var changed = false;

			var types = GetSecurableTypes();

			// DO NOT CONVERT TO LINQ (MUCH SLOWER)
			foreach (var securableType in types)
				changed |= EnsureSecurity(securableType, null);

			return changed;
		}

		/// <summary>
		/// Ensure filtered securable objects of specified type security assignments
		/// </summary>
		/// <returns>true, if security changed</returns>
		public virtual bool EnsureSecurity(SecurableType securableType, IDictionary<string, object> values)
		{
			if (securableType == null) throw new ArgumentNullException("securableType");
			if (values != null) Source.LoadValuesToCtx(values);

			using (new SecurableTypeContext(securableType))
			{
				var changed = false;

				var securables = Source.SearchSecurables(securableType);

				// DO NOT CONVERT TO LINQ (MUCH SLOWER)
				foreach (var securable in securables)
					changed |= EnsureSecurity(securableType, securable, null);

				return changed;
			}
		}

		/// <summary>
		/// Ensure filtered securable objects of specified type security assignments
		/// </summary>
		/// <returns>true, if security changed</returns>
		public virtual bool EnsureSecurity(SecurableType securableType, SecurableFilter filter, IDictionary<string, object> values)
		{
			if (securableType == null) throw new ArgumentNullException("securableType");
			if (filter == null) throw new ArgumentNullException("filter");

			if (values != null)
				Source.LoadValuesToCtx(values);

			using (new SecurableTypeContext(securableType))
			{
				var changed = false;

				var securables = Source.SearchSecurables(securableType, filter);

				// DO NOT CONVERT TO LINQ (MUCH SLOWER)
				foreach (var securable in securables)
					changed |= EnsureSecurity(securableType, securable, null);

				return changed;
			}
		}

		/// <summary>
		/// Ensure specified securable security assignments
		/// </summary>
		/// <returns>true, if security changed</returns>
		public virtual bool EnsureSecurity(string instanceId, IDictionary<string, object> values)
		{
			if (instanceId.IsNullOrEmpty()) throw new ArgumentNullException("instanceId");

			if (values != null)
				Source.LoadValuesToCtx(values);

			var securableType = Source.DetectSecurableType(instanceId);

			using (new SecurableTypeContext(securableType))
			{
				var securable = Source.LoadSecurable(instanceId);

				return EnsureSecurity(securable.SecurableType, securable, null);
			}
		}

		/// <summary>
		/// <para>Ensure specified securable security assignments.</para>
		/// <para>Warning! Should be called from security context and securable should be from security context (aka RunElevated for SharePoint)!</para>
		///  </summary>
		/// <returns>true, if security changed</returns>
		public virtual bool EnsureSecurity(SecurableType securableType, ISecurable securable, IDictionary<string, object> values)
		{
			if (securableType == null) throw new ArgumentNullException("securableType");
			if (securable == null) throw new ArgumentNullException("securable");

			if (values != null)
				Source.LoadValuesToCtx(values);

			var policy = Source.DetectSecurityPolicy(securableType, securable);

			var assignments = Concept.GetSecurityAssignments(securableType, securable, policy);

			return EnsureSecurity(assignments, null);
		}

		/// <summary>
		/// <para>Ensure specified securable security assignments.</para>
		/// <para>Warning! Should be called from security context and securable should be from security context (aka RunElevated for SharePoint)!</para>
		/// </summary>
		/// <returns>true, if security changed</returns>
		public virtual bool EnsureSecurity(SecuritySecurableAssignments securableAssignments, IDictionary<string, object> values)
		{
			if (securableAssignments == null) throw new ArgumentNullException("securableAssignments");

			if (values != null)
				Source.LoadValuesToCtx(values);

			var secType = securableAssignments.SecurableType;
			var sec = securableAssignments.Securable;

			if (secType.IsSecurityInherited(sec))
				secType.BreakSecurityInheritance(sec, securableAssignments.Inherit);

			return
				securableAssignments.Inherit
					? secType.EnsureSecurityInherit(sec, securableAssignments.Assignments, securableAssignments.CurrentScopeOnly)
					: secType.EnsureSecurityExact(sec, securableAssignments.Assignments, securableAssignments.CurrentScopeOnly);
		}
	}
}